package com.summer.ioc.container;

import com.summer.ioc.bean.annotation.*;
import com.summer.ioc.bean.definition.AbstractBeanDefinition;
import com.summer.ioc.bean.definition.BeanDefinition;
import com.summer.ioc.bean.definition.annotated.AbstractBeanAnnotatedBeanDefinition;
import com.summer.ioc.bean.definition.annotated.DefaultBeanAnnotatedBeanDefinition;
import com.summer.ioc.bean.definition.component.AbstractComponentBeanDefinition;
import com.summer.ioc.component.annotation.ComponentScan;
import com.summer.ioc.container.util.ComponentUtil;
import com.summer.ioc.exception.BeanException;
import com.summer.ioc.reflect.Parameter;
import com.summer.ioc.util.AnnotationUtil;
import com.summer.ioc.util.MethodUtil;
import com.summer.ioc.util.StringUtils;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * @author xiaoye
 * @create 2021-05-12 22:57
 */
public class AnnotatedIocContainer extends AbstractIocContainer{

    public AnnotatedIocContainer(Class configurationClass)
    {
        if (!AnnotationUtil.existAnnotation(configurationClass, Configuration.class))
        {
            throw new BeanException("["+configurationClass.getName()+"] 不是一个配置类");
        }
        parseConfigurationClass(configurationClass);
        init();
    }

    private void init() {
        initialBeans();
    }

    private void parseConfigurationClass(Class configurationClass) {
        var configurationClassBeanDefinition = (AbstractBeanDefinition) ComponentUtil.buildComponentBeanDefinition(configurationClass);
        registryBean(configurationClassBeanDefinition);
        Object configurationClassBeanObject = buildBean(configurationClassBeanDefinition);
        configurationClassBeanDefinition.setBeanObject(configurationClassBeanObject);
        parseBean(configurationClass);
    }

    private void parseBean(Class configurationClass) {
        scanComponent(configurationClass);

        parseBeanAnnotatedMethod(configurationClass);

    }

    private void parseBeanAnnotatedMethod(Class configurationClass) {
        String configurationClassBeanName = StringUtils.firstLetterToLowerCase(configurationClass.getSimpleName());
        Method[] methods = configurationClass.getMethods();
        for (Method method : methods) {
            if (AnnotationUtil.existAnnotation(method, Bean.class))
            {
                parseBeanAnnotatedMethod(method,getBean(configurationClassBeanName));
            }
        }
    }

    private void scanComponent(Class configurationClass) {
        if (AnnotationUtil.existAnnotation(configurationClass, ComponentScan.class))
        {
            ComponentScan componentScan = AnnotationUtil.getAnnotation(configurationClass, ComponentScan.class);
            String[] basePackages = componentScan.basePackages();
            if (basePackages.length > 0 && StringUtils.hasText(basePackages[0]))
            {
                scanComponent(basePackages);
            }
        }
    }

    private void parseBeanAnnotatedMethod(Method method,Object configurationClassObject) {
        Bean beanAnnotation = AnnotationUtil.getAnnotation(method, Bean.class);
        String name = null;
        if (StringUtils.hasText(beanAnnotation.name()))
            name = beanAnnotation.name();
        else
            name = method.getName();
        var bd = new DefaultBeanAnnotatedBeanDefinition(name, method.getReturnType());
        if (AnnotationUtil.existAnnotation(method, Scope.class))
            bd.setScope(AnnotationUtil.getAnnotation(method,Scope.class).value());
        if (AnnotationUtil.existAnnotation(method, LazyLoad.class))
            bd.setLazyLoad(AnnotationUtil.getAnnotation(method,LazyLoad.class).value());
        bd.setMethod(method);
        bd.setConfigurationClassObject(configurationClassObject);
        registryBean(bd);
    }

    @Override
    protected Object buildBean(BeanDefinition beanDefinition) {
        var beanDependencyNames = beanDefinition.getDependencyBeanNames();
        buildDependencyBeans(beanDependencyNames);
        Object bean = null;
        if (beanDefinition instanceof AbstractBeanAnnotatedBeanDefinition)
        {
            var bd = (AbstractBeanAnnotatedBeanDefinition)beanDefinition;
            Method method = bd.getMethod();
            Object[] params = buildParameters(method);
            Object configurationClassObject = bd.getConfigurationClassObject();

            try {
                bean = method.invoke(configurationClassObject,params);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        }
        else if (beanDefinition instanceof AbstractComponentBeanDefinition)
        {
            bean = ComponentUtil.buildComponentBean(beanDefinition,this);
        }

        ((AbstractBeanDefinition)beanDefinition).setBeanObject(bean);
        return bean;
    }

    private Object[] buildParameters(Method method) {
        Parameter[] parameters = MethodUtil.getParameters(method);
        Object[] params = new Object[parameters.length];
        if (AnnotationUtil.existAnnotation(method, Autowired.class))
        {

            for (int i = 0; i < parameters.length; i++) {
                params[i] = getBean(parameters[i].getName());
            }
            return params;
        }
        else
        {
            for (int i = 0; i < parameters.length; i++) {
                if (AnnotationUtil.existAnnotation(parameters[i].getParameter(),Autowired.class))
                    params[i] = getBean(parameters[i].getName());
                else
                    params[i] = null;
            }

        }
        return params;
    }
}
